package com.hy.proxy.core.util;

import com.hy.proxy.common.LogUtils;
import com.hy.proxy.core.Config;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.select.Elements;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

/**
 * @Author: hy
 * @Date: Created in 2019/9/26/13:55
 * @Description:
 * @Version: 1.0
 */
@Slf4j
public class SeleniumUtils {
//    //注意
//    注意
//     下面注释中有关附加路径的注释和猜测都是错误的，原因只是因为报错信息中，Selenium取了getPath然后和传入的路径链接起来了。
//    注意
//    注意
//
//    static {
//
//        String os = System.getProperty("os.name");
////        String userDir = System.getProperty("user.dir");
//        String webDriverPath = SeleniumUtils.class.getResource("/static/webdriver").getFile();
//
//        //在Tomcat War or Jar 中开头会附加 file:/ ;然后不知道为什么在 setProperty 时在前面又追加一个类似 user.dir 的路径
//        log.info("Code Block: WebDriverPath - " + webDriverPath);
//        log.info("Code Block: WebDriverPath - " + webDriverPath.substring(1));
//        log.info("Code Block: WebDriverPath - " + webDriverPath.substring(6));
//
//        //ChromeDriver服务地址
//        if (os.toLowerCase().startsWith("win")) {
//            log.info("Code Block: 系统为" + os + ",将加载 chromedriver2.exe 驱动");
//            //Windows需要截取开头的 /
//            String winPath = webDriverPath.substring(1) + "/chromedriver2.exe";
//            //selenium 会检测 Property Value 是不是绝对路径，如果不是将会自动追加 user.dir
//            //selenium 用传入的 Property Value 构建了一个 File，当报错时会用 file.getAbsolutePath() 取 Property Value 的绝对路径
//            //DriverService.java 118行
//            System.setProperty("webdriver.chrome.driver", winPath);
//
//        } else {
//            log.info("Code Block: 系统为" + os + ",将加载 chromedriver 驱动");
//            String linuxPath = webDriverPath.substring(6) + "/chromedriver";
//            System.setProperty("webdriver.chrome.driver", linuxPath);
//        }
//    }

    static {
        new Config().setWebDriverPath();
    }

    /**
     * Chrome选项
     */
    private volatile static ChromeOptions chromeOptions = null;

    /**
     * 获取一个设置完成的ChromeOptions
     *
     * @return ChromeOptions
     */
    //TODO 无头模式下只有 ssrtool 这个网站无法加载数据，需要输入验证码
    public static ChromeOptions getChromeOptions() {
        if (chromeOptions == null) {
            synchronized (SeleniumUtils.class) {
                if (chromeOptions == null) {
                    //用于excludeSwitches参数
                    ArrayList<String> arg = new ArrayList<>();
                    arg.add("enable-automation");

                    //创建新的Chrome选项
                    chromeOptions = new ChromeOptions()
                            //设置启动为无头模式
//                            .addArguments("--headless")
                            .addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36")
                            .addArguments("--disable-gpu")
                            .addArguments("--disable-dev-shm-usage")
                            .addArguments("--no-sandbox")
                            .addArguments("lang=zh_CN.UTF-8")
                            //设置Chrome启动时的参数,忽略证书（SSL）错误
                            .addArguments("--ignore-certificate-errors")
                            //设置Chrome启动时的参数,设置窗口大小
                            .addArguments("----window-size=50,50")
                            //开启一个实验性参数excludeSwitches，用来隐藏window.navigator.webdriver返回true,这个参数必须是List
                            .setExperimentalOption("excludeSwitches", arg);

//                    chromeOptions.setProxy()

//                    chromeOptions.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
//                    chromeOptions.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);

                    return chromeOptions;
                }
            }
        }
        return chromeOptions;
    }

    /**
     * 获取一个新的ChromeDriver
     */
    public static ChromeDriver getChromeDriver() {

        ChromeDriver chromeDriver = new ChromeDriver(getChromeOptions());

        chromeDriver.manage().timeouts().implicitlyWait(50, TimeUnit.SECONDS);
        chromeDriver.manage().timeouts().setScriptTimeout(50, TimeUnit.SECONDS);
        chromeDriver.manage().timeouts().pageLoadTimeout(50, TimeUnit.SECONDS);

        return chromeDriver;
    }


    /**
     * 使用 SeleniumDriver 解析 指定url，返回源码
     * <p>
     * （失败 free-ss， 使用 HttpUnitDriver，freeSS 进入 about:blank）暂不清楚原因
     * （成功 free-ss，使用 ChromeDriver 成功获取 源码）
     *
     * @param wd 驱动
     * @return 网页源码
     */
    public static String getPageSource(WebDriver wd, String url) {

        //在当前页面加载一个新的Web页面，使用HTTP GET操作完成的，该方法将阻塞，直到页面加载完成（不算ajax）。
        wd.get(url);

        //等待页面加载完成
        try {
            Thread.sleep(1000);
            isLoading(wd);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info(LogUtils.getCurrentMethodName() + ": " + url + " 加载完成。");

        return wd.getPageSource();

    }

    /**
     * 等待网站加载完成（ajax）
     *
     * @param wd 要等待的网站
     * @throws InterruptedException
     */
    private static void isLoading(WebDriver wd) throws InterruptedException {

        boolean ajaxLoading = false;
        boolean docLoading = false;

        //最多容忍次数
        int i = 10;

        //获取还未加载ajax的源码，用于判断是否是json请求
        String pageSource = wd.getPageSource();
        if (isJsonRequest(pageSource)) {
            //是json请求直接退出
            return;
        }

        do {
            try {
                //判断网站文档和所有子资源是否以完成加载，完成加载返回true
                docLoading = (boolean) ((ChromeDriver) wd).executeScript("return document.readyState == 'complete'");

                ajaxLoading = (boolean) ((ChromeDriver) wd).executeScript("return jQuery.active == 0");

//                if (docLoading) {
//                    Thread.sleep(1500);
//                    //如果i已经一半了ajaxLoading还是false,则不继续等待
//                    if (i == (i / 2)) {
//                        ajaxLoading = true;
//                    }
//                    //判断网站ajax调用是否结束,结束返回true
//                    //有些网站可能没用jq,所以这个会一直catch.
//                    ajaxLoading = (boolean) ((ChromeDriver) wd).executeScript("return jQuery.active == 0");
//                }

                //使用了chromeOptions.setExperimentalOption("excludeSwitches", new ArrayList<>().add("enable-automation")); 可以省略这行代码了
                ((ChromeDriver) wd).executeScript("Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});");
                ((ChromeDriver) wd).executeScript("window.chrome = {runtime: {},app:{},csi:{},loadTimes:{}}");
                ((ChromeDriver) wd).executeScript("Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5],});");
                ((ChromeDriver) wd).executeScript("Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en'],});");

            } catch (Exception e) {
                log.info(LogUtils.getCurrentMethodName() + ": " + wd.getCurrentUrl() + " 网站加载中... 剩余尝试次数" + i + "次");

                i--;

                //未加载完成就等一会
                Thread.sleep(1000);
            }

        } while ((!ajaxLoading) && (!docLoading) && (i != 0));

        //等待Ajax数据渲染
        Thread.sleep(2500);

    }

    /**
     * 验证是否是json请求
     *
     * @param pageSource
     * @return false不是json请求，true是json请求
     */
    private static boolean isJsonRequest(String pageSource) {

        if ((pageSource != null) && (!pageSource.isEmpty())) {

            Elements pre = Jsoup.parse(pageSource).getElementsByTag("pre");

            //如果等于1的话就有可能是一个json请求，进行二次验证
            if (pre.size() == 1) {

                String preText = pre.get(0).text().trim().substring(0, 1);
                //一个为true就确定为json请求
                return preText.contains("{") || preText.contains("[");
            }
        }
        return false;
    }


}
